﻿using System;
using System.Xml;
using Notoric.Model.Internal;
using Notoric.Model.Packaging;

namespace Notoric.Model.Elements.CWNotation
{
    /// <summary>
    /// Represents one key signature sign.
    /// </summary>
    /// <remarks>
    /// This class represents one accidental sign (including no accidental sign)
    /// of a key signature. It is implemented as a stand-alone class to enable
    /// an easy layouting and ordering of the accidental signs in the key signature.
    /// </remarks>
    public class KeySignatureSign : ModelElement
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="KeySignatureSign"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="KeySignature"/>
        /// </para>
        /// <para>
        /// The model element that directly owns this element.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="packagePart"/> or <paramref name="owner"/> is <see langword="null"/>.
        /// </exception>
        internal KeySignatureSign(XmlPackagePart packagePart, KeySignature owner)
            : base(packagePart, owner)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");

            accidentalTypeAttribute.Value = CWNotation.AccidentalType.None.ToString();
            positionAttribute.Value = (0.0).ToString(PackageVocabulary.PackageCulture.NumberFormat);
            staffPositionAttribute.Value = "0";
        }

        /// <summary>
        /// Creates a new <see cref="KeySignatureSign"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="KeySignature"/>
        /// </para>
        /// <para>
        /// The model element that directly owns this element.
        /// </para>
        /// </param>
        /// <param name="element">
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The XML element that holds the data of this object.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        internal KeySignatureSign(XmlPackagePart packagePart, KeySignature owner, XmlElement element)
            : base(packagePart, owner, element)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");

            accidentalTypeAttribute = element.Attributes[PackageVocabulary.AccidentalTypeAttribute];
            positionAttribute = element.Attributes[PackageVocabulary.PositionAttribute];
            staffPositionAttribute = element.Attributes[PackageVocabulary.StaffPositionAttribute];
        }

        #endregion

        #region ModelElement Members

        /// <inheritdoc/>
        protected override XmlElement CreateObjectElement(XmlDocument document)
        {
            var element = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.KeySignatureSignElementName,
                PackageVocabulary.PackageNamespaceUri
                );

            positionAttribute = document.CreateAttribute(PackageVocabulary.PositionAttribute);
            staffPositionAttribute = document.CreateAttribute(PackageVocabulary.StaffPositionAttribute);
            accidentalTypeAttribute = document.CreateAttribute(PackageVocabulary.AccidentalTypeAttribute);

            element.Attributes.Append(positionAttribute);
            element.Attributes.Append(staffPositionAttribute);
            element.Attributes.Append(accidentalTypeAttribute);

            return element;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the type of the accidental.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="CWNotation.AccidentalType"/>
        /// </para>
        /// <para>
        /// The type of the accidental.
        /// </para>
        /// </value>
        public AccidentalType AccidentalType
        {
            get { return (CWNotation.AccidentalType)Enum.Parse(typeof(CWNotation.AccidentalType), accidentalTypeAttribute.Value); }
            set
            {
                accidentalTypeAttribute.Value = value.ToString();

                RaisePropertyChanged("AccidentalType");
            }
        }

        /// <summary>
        /// Gets or sets the position of the sign.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The distance (in centimeters) between the sign's left edge 
        /// and the owner <see cref="KeySignature"/>'s left edge.
        /// </para>
        /// </value>
        public double Position
        {
            get { return double.Parse(positionAttribute.Value, PackageVocabulary.PackageCulture.NumberFormat); }
            set
            {
                positionAttribute.Value = value.ToString(PackageVocabulary.PackageCulture.NumberFormat);

                RaisePropertyChanged("Position");
            }
        }

        /// <summary>
        /// Gets or sets the staff position of the sign.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// The staff position of the sign.
        /// </para>
        /// </value>
        public int StaffPosition
        {
            get { return int.Parse(staffPositionAttribute.Value, PackageVocabulary.PackageCulture.NumberFormat); }
            set
            {
                staffPositionAttribute.Value = value.ToString(PackageVocabulary.PackageCulture.NumberFormat);

                RaisePropertyChanged("StaffPosition");
            }
        }

        /// <summary>
        /// Gets the width of the sign.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The width (in centimeters) of the sign.
        /// </para>
        /// </value>
        public double Width
        {
            get
            {
                double baseSize = FindOwner<Staff>().SpaceSize;

                if (AccidentalType == AccidentalType.DoubleNeutral || AccidentalType == AccidentalType.DoubleFlat)
                    return 2 * baseSize;
                else if (AccidentalType == AccidentalType.None)
                    return 0;
                else
                    return baseSize;
            }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the value of the <see cref="AccidentalType"/> property.
        /// </summary>
        private XmlAttribute accidentalTypeAttribute;
        /// <summary>
        /// Holds the value of the <see cref="Position"/> property.
        /// </summary>
        private XmlAttribute positionAttribute;
        /// <summary>
        /// Holds the value of the <see cref="StaffPosition"/> property.
        /// </summary>
        private XmlAttribute staffPositionAttribute;

        #endregion
    }
}
